package com.cjf.aop.aspect;

import android.util.Log;

import com.cjf.aop.demo.SecurityCheckAnnotation;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.SourceLocation;


/**
 * Join Points（以后简称JPoints）是 AspectJ 中最关键的一个概念，就是程序运行时的一些 执行点。
 * 1.一个函数的调用可以是一个 JPoint。比如 Log.e()这个函数。e 的执行可以是一
 * 个 JPoint，而调用 e 的函数也可以认为是一个 JPoint。
 * 2.设置一个变量，或者读取一个变量，也可以是一个 JPoint。比如 Demo 类中有
 * 一个 debug的 boolean 变量。设置它的地方或者读取它的地方都可以看做是 JPoints。
 * 3.for 循环可以看做是 JPoint
 * <p>
 * AspectJ 所认可的 JoinPoints 的类型:
 * method call  函数调用   比如调用 Log.e()，这是一处 JPoint
 * method execution  函数执行        比如 Log.e()的执行内部，是一处JPoint
 * constructor call  构造函数调用    和 method call 类似
 * constructor execution  构造函数执行   和 method execution 类似
 * field get  获取某个变量    比如读取 DemoActivity.debug 成员
 * field set  设置某个变量     比如设置 DemoActivity.debug 成员
 * pre-initializationObject 在构造函数中做得一些工作。 很少使用
 * initialization  Object 在构造函数中做得工作
 * static initialization  类初始化  比如类的 static{}
 * handler  异常处理  比如 try catch(xxx)中，对应 catch 内的执行
 * advice execution这个是 AspectJ 的内容，稍后再说
 * <p>
 * Pointcuts ，Pointcuts 的目标是提供一种方法使得开发者能够选择自己感兴趣的 JoinPoints
 * <p>
 * public pointcut testAll(): call(public * *.println(..)) && !within(TestAspect) ;
 * 我们来看看上述代码
 *   第一个 public：表示这个 pointcut 是 public 访问。这主要和 aspect 的继承关系有关，属于 AspectJ 的高
 * 级玩法，本文不考虑。
 *   pointcut：关键词，表示这里定义的是一个 pointcut。pointcut 定义有点像函数定义。总之，在 AspectJ
 * 中，你得定义一个 pointcut。
 *   testAll()：pointcut 的名字。在 AspectJ 中，定义 Pointcut 可分为有名和匿名两种办法。个人建议使用
 * named 方法。因为在后面，我们要使用一个 pointcut 的话，就可以直接使用它的名字就好。
 *   testAll 后面有一个冒号，这是 pointcut 定义名字后，必须加上。冒号后面是这个 pointcut 怎么选择
 * Joinpoint 的条件。
 *   本例中，call(public * *.println(..))是一种选择条件。call 表示我们选择的 Joinpoint 类型为 call 类型。
 *   public * *.println(..)：这小行代码使用了通配符。由于我们这里选择的 JoinPoint 类型为 call 类型，它
 * 对应的目标 JPoint 一定是某个函数。所以我们要找到这个/些函数。public 表示目标 JPoint 的访问类
 * 型（public/private/protect）。第一个*表示返回值的类型是任意类型。第二个*用来指明包名。此处不
 * 限定包名。紧接其后的 println 是函数名。这表明我们选择的函数是任何包中定义的名字叫 println 的
 * 函数。当然，唯一确定一个函数除了包名外，还有它的参数。在(..)中，就指明了目标函数的参数应该
 * 是什么样子的。比如这里使用了通配符..，代表任意个数的参数，任意类型的参数。
 *   再来看 call 后面的&&：AspectJ 可以把几个条件组合起来，目前支持 &&，||，以及！这三个条件。
 * 这三个条件的意思不用我说了吧？和 Java 中的是一样的。
 *   来看最后一个!within(TestAspectJ)：前面的!表示不满足某个条件。within 是另外一种类型选择方法，
 * 特别注意，这种类型和前面讲到的 joinpoint 的那几种类型不同。within 的类型是数据类型，而 joinpoint
 * 的类型更像是动态的，执行时的类型。
 * <p>
 * 上例中的 pointcut 合起来就是：
 * 1.选择那些调用 println（而且不考虑 println 函数的参数是什么）的 Joinpoint。
 * 2.另外，调用者的类型不要是 TestAspect 的。
 * <p>
 * 不同类型的 JPoint 对应的 pointcuts 查询方法
 * Join point category          Pointcut syntax
 * -------------------------------------------------------------
 * Method   execution           execution (MethodSianature)
 * Method call                  cal1 (MethodSignature)
 * Constructor execution        execution (ConstructorSignature)
 * Constructor call             cal1 (ConstructorSignature)
 * Class initialization         staticinitialization (Typesianature)
 * Field read access            get (FieldSiqnature)
 * Field write access           set (FieldSianature)
 * Exception handler execution  handler(TypeSianature)
 * Obiect initialization        initialization (Constructorsianature)
 * Obiect pre-initialization    preinitialization (ConstructorSiqnature)
 * Advice execution             adviceexecution()
 * <p>
 * <p>
 * advice，advice 就是一种 Hook，根据 JPoint 执行时机的不同而有不同的Hook
 * AspectJ支持的Advice 的类型有:
 * before()  表示在 JPoint 执行之前，需要干的事情
 * after()   表示 JPoint 自己执行完了后，需要干的事情。
 * after():returning ( 返 回 值类型)  after():throwing ( 异 常 类型)
 * returning 和 throwing 后面都可以指定具体的类型，如果不指定的话则匹配的时候不限定类型
 * 假设JPoint是一个函数调用的话，那么函数调用执行完有两种方式退出，一个是正常的 return，另外
 * 一个是抛异常。注意，after()默认包括 returning 和throwing 两种情况
 * 返回值类型 around()
 * before和around是指JPoint执行前或执行后备触发，而around就替代了原JPoint
 * around 是替代了原 JPoint，如果要执行原 JPoint 的话，需要调用proceed()
 * (PS:简单来说就是原JPoint执行被拦截了，除非在around()内部调用proceed()方法，才放行
 * 注意：从技术上说，around 是完全可以替代 before 和 after 的。
 * created by cjf
 * on: 2020/3/18
 */
@Aspect //必须使用@AspectJ 标注，这样 class DemoAspect 就等同于 aspect DemoAspect 了
public class MainAspect {
    private static final String TAG = "MainAspect";

    //仅列出 onCreate 和 onStart 两个函数的日志
   /* @Pointcut("execution(protected * com.cjf.aop.MainActivity.onCreate(..))||" +
            "execution(protected * com.cjf.aop.MainActivity.onStart(..))||")*/
    //在所有的包下的MainActivity类的onXXX/这样的函数里加上 log
    //@Pointcut("execution(* *..MainActivity.on*(..))")

    //在com.cjf.aop包以及子包下的MainActivity类的onXXX/这样的函数里加上 log
    //@Pointcut("execution(* com.cjf.aop..*MainActivity.on*(..))")
    //打开某个页面时，查找该页面内任意公共方法的执行
    //@Pointcut("execution(public * *(..))")
    //在com.cjf.aop.MainActivity这个类函数里加上log
    @Pointcut("execution(* com.cjf.aop.MainActivity.on*(..))")
    public void logForActivity() {//注意，这个函数必须要有实现，否则 Java 编译器会报错
    }

    /*
    @Before：这就是 Before的advice，对于after，after-returning，和after-throwing。对应的注解格式为
    @After，@AfterReturning，@AfterThrowing。Before 后面跟的是 pointcut 名字，然后其代码块由一个函数
    来实现。比如此处的 log。
    */
    @Before("logForActivity()")
    public void log(JoinPoint joinPoint) {
        //对于使用 Annotation 的 AspectJ 而言，JoinPoint 就不能直接在代码里得到多了，而需要通过
        //参数传递进来。
        //打印信息包含访问限制、包名、类名、方法名、方法参数类型
        //Log.e(TAG, joinPoint.toLongString());
        //只包含类名、方法名
        Log.e(TAG, joinPoint.toShortString());
        //在这里拿到的都是对应包名下调用方法的MainActivity对象
        Object aThis = joinPoint.getThis();
        Object target = joinPoint.getTarget();
        //Log.e(TAG, "getThis=" + joinPoint.getThis().toString() + "; getTarget=" + joinPoint.getTarget().toString());
        Object[] args = joinPoint.getArgs();
        //注解
        String kind = joinPoint.getKind();
        Signature signature = joinPoint.getSignature();
        SourceLocation sourceLocation = joinPoint.getSourceLocation();
        String s = joinPoint.getStaticPart().toString();
    }

    @Pointcut("set(int com.cjf.aop.MainActivity.*)")
    public void field_signature(){

    }

    @Before("field_signature()")
    public void field_signature_before(JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();
        StringBuilder builder=new StringBuilder();
        for (Object o:args){
            builder.append("arg=");
            builder.append(o.toString());
            builder.append(";");
        }
        Log.e(TAG, "before calling:" + joinPoint);
        Log.e(TAG, "args: " + builder.toString());
        Log.e(TAG, "at " + joinPoint.getSourceLocation());
    }

    /*来看这个 Pointcut，首先，它在选择 JPoint 的时候，把@SecurityCheckAnnotation 使用上了，这表明所有那
        些访问权限为public的，并且携带有这个注解的API都是目标JPoint
    接着，由于我们希望在函数中获取注解的信息，所有这里的 pointCut 函数有一个参数，参数类型是
        SecurityCheckAnnotation，参数名为 ann
    这个参数我们需要在后面的 advice 里用上，所以 pointcut 还使用了@annotation(ann)这种方法来告诉
        AspectJ，这个 ann 是一个注解*/
    //访问权限 public 返回值任意 *  任意包名 *.. 任意类名 *.  任意函数名 * 函数参数任意 ..
    @Pointcut("execution(@com.cjf.aop.demo.SecurityCheckAnnotation public * *..*.*(..)) && @annotation(ann)")
    public void checkPermission(SecurityCheckAnnotation ann) {

    }

    /*
    接下来是advice，advice的真正功能由check函数来实现，这个check函数第二个参数就是我们想要
    的注解。在实际运行过程中，AspectJ 会把这个信息从 JPoint 中提出出来并传递给check函数。
    */
    @Before("checkPermission(securityCheckAnnotation)")
    public void check(JoinPoint joinPoint,SecurityCheckAnnotation securityCheckAnnotation){
        //从注解信息中获取声明的权限。
        String neededPermission = securityCheckAnnotation.declaredPermission();
        Log.e(TAG, joinPoint.toShortString());
        Log.e(TAG, "\tneeded permission is " + neededPermission);
        return;
    }
    /*  @Pointcut("execution(public void android.view.View.OnClickListener.onClick(android.view.View))")
    public void pointOnClick() {//加了以后触发AOP位置的断点无效

    }

    @Around("pointOnClick()")
    public void onClickAroundJoinPoint(JoinPoint joinPoint) {
        Log.e(TAG, "触发点击事件:" + joinPoint);
    }*/

   /* @Pointcut("execution(void *..lambda*(*..view.View))")
    public void pointOnClickLambda(){//加了以后触发AOP位置的断点无效

    }

    @Around("pointOnClickLambda()")
    public void aroundOnClickLambda(JoinPoint joinPoint){
        Log.e(TAG, "触发点击事件:(lambda)" + joinPoint);
    }*/
}
